﻿/*
 * @author: wizardc
 */

using System.Runtime.CompilerServices;
using Unity.Burst;
using Unity.Mathematics;
using UnityEngine;

namespace Dou.Clip
{
    /// <summary>
    /// 视椎体裁剪
    /// </summary>
    public sealed class FrustumClipping
    {
        private readonly Plane[] _frustumPlanes = new Plane[6];
        
        public void Update(Camera camera)
        {
            GeometryUtility.CalculateFrustumPlanes(camera, _frustumPlanes);
        }

        [BurstCompile]
        public FrustumClippingType GetClipping(ref Bounds bounds)
        {
            float3 center = bounds.center;
            float3 extents = bounds.extents;
            ref var plan0 = ref _frustumPlanes[0];
            ref var plan1 = ref _frustumPlanes[1];
            ref var plan2 = ref _frustumPlanes[2];
            ref var plan3 = ref _frustumPlanes[3];
            ref var plan4 = ref _frustumPlanes[4];
            ref var plan5 = ref _frustumPlanes[5];
            return GetClipping(ref plan0, ref plan1, ref plan2, ref plan3, ref plan4, ref plan5, ref center, ref extents);
        }
        
        [BurstCompile]
        public FrustumClippingType GetClipping(ref float4x4 matrix)
        {
            float3 center = matrix.c3.xyz;
            float3 extents = ((Matrix4x4) matrix).lossyScale;
            ref var plan0 = ref _frustumPlanes[0];
            ref var plan1 = ref _frustumPlanes[1];
            ref var plan2 = ref _frustumPlanes[2];
            ref var plan3 = ref _frustumPlanes[3];
            ref var plan4 = ref _frustumPlanes[4];
            ref var plan5 = ref _frustumPlanes[5];
            return GetClipping(ref plan0, ref plan1, ref plan2, ref plan3, ref plan4, ref plan5, ref center, ref extents);
        }
        
        [BurstCompile]
        public FrustumClippingType GetClipping(float3 center, float3 extents)
        {
            ref var plan0 = ref _frustumPlanes[0];
            ref var plan1 = ref _frustumPlanes[1];
            ref var plan2 = ref _frustumPlanes[2];
            ref var plan3 = ref _frustumPlanes[3];
            ref var plan4 = ref _frustumPlanes[4];
            ref var plan5 = ref _frustumPlanes[5];
            return GetClipping(ref plan0, ref plan1, ref plan2, ref plan3, ref plan4, ref plan5, ref center, ref extents);
        }
        
        [BurstCompile]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private FrustumClippingType GetClipping(ref Plane plane0, ref Plane plane1, ref Plane plane2, ref Plane plane3, ref Plane plane4, ref Plane plane5, ref float3 center, ref float3 extents)
        {
            int side = 0;
            bool cross = false;
            side = GetPlaneSide(ref plane0, ref center, ref extents);
            if (side == -1) return FrustumClippingType.Out;
            if (side == 0) cross = true;
            side = GetPlaneSide(ref plane1, ref center, ref extents);
            if (side == -1) return FrustumClippingType.Out;
            if (side == 0) cross = true;
            side = GetPlaneSide(ref plane2, ref center, ref extents);
            if (side == -1) return FrustumClippingType.Out;
            if (side == 0) cross = true;
            side = GetPlaneSide(ref plane3, ref center, ref extents);
            if (side == -1) return FrustumClippingType.Out;
            if (side == 0) cross = true;
            side = GetPlaneSide(ref plane4, ref center, ref extents);
            if (side == -1) return FrustumClippingType.Out;
            if (side == 0) cross = true;
            side = GetPlaneSide(ref plane5, ref center, ref extents);
            if (side == -1) return FrustumClippingType.Out;
            if (side == 0) cross = true;
            return cross ? FrustumClippingType.Cross : FrustumClippingType.Inner;
        }
        
        [BurstCompile]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int GetPlaneSide(ref Plane p, ref float3 center, ref float3 extents)
        {
            float3 n = p.normal;
            float r = math.dot(extents, math.abs(n));
            float s = math.dot(n, center) + p.distance;
            if (math.abs(s) <= r) return 0;
            return s > 0 ? 1 : -1;
        }
    }
}
